/*****************************************************************************
 * $LastChangedDate: 2011-04-23 21:07:07 -0400 (Sat, 23 Apr 2011) $
 * @file
 * @author  Jim E. Brooks  http://www.palomino3d.org
 * @brief   Sky dome.
 *//*
 * LEGAL:   COPYRIGHT (C) 2008 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE VERSION 2 (GPL 2).
 *****************************************************************************/

#ifndef SKY_SKY_DOME_HH
#define SKY_SKY_DOME_HH 1

#include <utility>
#include <vector>
#include "base/array_small.hh"
#include "base/array_2d.hh"
using namespace base;
#include "math/module.hh"
#include "math/funcs.hh"
using namespace math;
#include "world/debug.hh" // ASSERT_LAT_LON_DEG()

namespace sky {

////////////////////////////////////////////////////////////////////////////////
/// @brief Sky dome.
///
/// Latitude vs. longitude:
/// -----------------------
/// Latitude defines a half-circle {0',...,180'}.
/// Longitude defines a full-circle {0',...,360'}.
///
/// SkyDome vs. SkyDomePainter:
/// ---------------------------
/// SkyDome is a general data container (in terms of vertex colors).
/// Algorithms to paint the sky are innately specific,
/// so they're separated as the SkyDomePainter class.
/// This separation would allow painting an Earth or Martian sky
/// using different SkyDomePainter implementations.
///
/// Implementation of the sky dome:
/// -------------------------------
/// The construction of the sky dome accords with the requirement that
/// a 2D (lat,lon) coordinate has to be correlated to a 3D vertex,
/// and also to OSG nodes in order to modify a vertex's color.
///
/// The first phase is a multi-level loop that computes vertexs
/// of a ring on the dome where a latitude indexes a ring,
/// and a longitude indexes a vertex in the ring.
/// The OSG nodes for a vertex on the dome is collected into
/// the SkyDome::Point class, and points are organized as a 2D array.
///
/// The second phase builds graphics primitives for each patch of the dome.
/// Imagine the dome divided into horizontal bands (two connected rings),
/// then each band is divided into quads.
///
/// When a client (SkyDomePainter) requests to change a color by (lat,lon),
/// SkyDome does a lookup into the 2D array of Points.
/// Note that one Point will be shared by multiple quads,
/// each having its own color array, so more than one color array (etc)
/// must be updated for a color modification.  Therefore, the OSG nodes
/// for a patch of the dome is defined by the class Patch,
/// and a Point will have multiple references to a Patch.
///
/// Edge-cases (implementation):
/// ----------------------------
/// The 2D array of Points is plus one.
/// lon=0 and lon=360 reference different vertexs.
/// A bad design would be to require the client to make two calls
/// (worse, 360 is technically an overrun case).
/// SetColor( color, lat=0, lon=0 );
/// SetColor( color, lat=0, lon=360 );
/// The implementation, as a special-case, will automatically change
/// both vertexs if lon is almost equal to 0.0 or 360.0.
///
/// Problems:
/// ---------
/// Increasing sky dome will affect the view frustum.
/// If its radius is too large, frustum clipping will occur.
///
class SkyDome : public Object
{
PREVENT_COPYING(SkyDome)
friend class shptr<SkyDome>;
friend class Sky;
friend class Point;
friend class Patch;
private:  // only Sky can instantiate
                            SkyDome( const WorldVertex& center, const fp radius, const RGBA color, const int sliceCount );
    virtual                 ~SkyDome();

public:
// Object interface:
// GetPosition() inherited from Object class.
// GetRadius() is overridden since the radius computed by Object isn't precise enough.
    virtual fp              GetRadius( void ) { ASSERT(mRadius > 0); return mRadius; }
    virtual NodeSort        GetNodeSort( void );

// SkyDome specific:
    WorldVertex             ComputeVertexOnDome( const Degree lat, const Degree lon, const fp radiusScale );
    uint                    GetPointCount( void ) { return mPoints.size(); }  // flat count
    void                    SetColor( const RGBA color, const Degree lat, const Degree lon );
    void                    SetColor( const RGBA color, const uint flatIdx );
    void                    SetColorAll( const RGBA color );
    RGBA                    GetColor( const Degree lat, const Degree lon );
    uint                    GetSliceCount( void ) const
                            { ASSERT(mLonSliceCount > 0); return mLonSliceCount; }  // return longitude (has the value client passed prior)
    bool                    IfInside( const WorldVertex& pos, const fp radius );

private:
    void                    SetColor_( const RGBA color, const std::pair<int,int> idx );

private:
    /// @brief Defines one vertex on dome which will be shared by multiple Patches.
    /// Using an index, rather than a ptr, avoids the invalid iterator pitfall.
    ///
    class Point // : public Shared
    {
    COPYABLE(Point)
    public:
                    Point( void ) : mPos(0,0,0), mColor(), mPatchIdxs()  // because of STL
                    {
                        SET_TYPESIG(this,TYPESIG_SKY_DOME_POINT);
                    }
                    ~Point()
                    {
                        INVALIDATE_TYPESIG(this,TYPESIG_SKY_DOME_POINT);
                    }
        void        SetPosition( const WorldVertex& pos )
                    {
                    CHECK_TYPESIG(this,TYPESIG_SKY_DOME_POINT);
                        mPos = pos;
                    }
        WorldVertex GetPosition( void ) const
                    {
                    CHECK_TYPESIG(this,TYPESIG_SKY_DOME_POINT);
                        return mPos;
                    }
        void        SetColor( const RGBA color, SkyDome& skyDome );
        RGBA        GetColor( void ) const
                    {
                    CHECK_TYPESIG(this,TYPESIG_SKY_DOME_POINT);
                        return mColor;
                    }
    public:
        WorldVertex                       mPos;           ///< position of point
        RGBA                              mColor;
        SmallArray<4,std::pair<int,int> > mPatchIdxs;     ///< indexs of Patches that share this point (SkyDome::mPatches[])
        DECLARE_TYPESIG(TYPESIG_SKY_DOME_POINT)
    };

private:
    /// @brief Defines a quad of sky-dome.
    ///
    class Patch // : public Shared
    {
    COPYABLE(Patch)
    public:
                Patch( void ) : mBuilt(false), mGeode(NULL), mGeom(NULL), mPointIdxs()
                {
                    SET_TYPESIG(this,TYPESIG_SKY_DOME_PATCH);
                }
                ~Patch()
                {
                    INVALIDATE_TYPESIG(this,TYPESIG_SKY_DOME_PATCH);
                }
        void    BuildNodes( SkyDome& skyDome, osg::Group& group );
        void    SetExcluded( void ) { mBuilt = false; }
        bool    IfExcluded( void ) const { return not mBuilt; }
        void    UpdateColors( SkyDome& skyDome );

    public:
        bool                             mBuilt;             ///< false to bypass rendering
        RefPtr<osg::Geode>               mGeode;
        RefPtr<osg::Geometry>            mGeom;
        std::vector<std::pair<int,int> > mPointIdxs;         ///< index to SkyDome::mPoints
        DECLARE_TYPESIG(TYPESIG_SKY_DOME_PATCH)
    };

private:
    // Convert a float (lat,lon) into an index of Array2D.
    std::pair<int,int> Deg2Idx( const Degree lat, const Degree lon )
    {
    // Latitude defines a half-circle, longitude a full-circle.
    ASSERT_LAT_LON_DEG( lat, lon );
        const int iLat = int( (fp(lat) / 180.0f) * mLatSliceCount );
        const int iLon = int( (fp(lon) / 360.0f) * mLonSliceCount );
        return std::make_pair( iLat, iLon );  // rely on Array2D clamping indexs
    }

    // Get a Point by its 2D idx.
    Point& GetPoint( const std::pair<int,int> idx )
    {
    CHECK_TYPESIG( &mPoints.Get( idx.first, idx.second ), TYPESIG_SKY_DOME_POINT );
        return mPoints.Get( idx.first, idx.second );
    }

    // Get a Patch by its 2D idx.
    Patch& GetPatch( const std::pair<int,int> idx )
    {
    CHECK_TYPESIG( &mPatches.Get( idx.first, idx.second ), TYPESIG_SKY_DOME_PATCH );
        return mPatches.Get( idx.first, idx.second );
    }

    // Associate a point with a patch and v.v.
    void JoinPointAndPatch( const int pointLat, const int pointLon,
                            const int patchLat, const int patchLon )
    {
        // Attach Point to Patch.
        // And v.v.
        Point& point = mPoints.Get(  pointLat, pointLon );
        Patch& patch = mPatches.Get( patchLat, patchLon );
        patch.mPointIdxs.push_back( std::make_pair( pointLat, pointLon ) );
        point.mPatchIdxs.push_back( std::make_pair( patchLat, patchLon ) );
    }

private:
    // SkyDome::mRadius is more precise than Object::mRadius
    fp                  mRadius;            ///< HIDES Object::mRadius (ok, better than mDomeRadius)
    RGBA                mColor;             ///< default color
    const int           mLatSliceCount;     ///< latitude slices
    const int           mLonSliceCount;     ///< longitude slices
    Array2D<Point>      mPoints;            ///< vertexs on dome (rely on Array2D clamping indexs)
    Array2D<Patch>      mPatches;           ///< primitives of dome
    const Degree        mLatLimit;          ///< latitudes beyond this won't be drawn
public:
    DECLARE_TYPESIG(TYPESIG_SKY_DOME)
};

} // namespace sky

#endif // SKY_SKY_DOME_HH
